use std::sync::Arc;

use axum::{extract::Request, http::header, middleware::Next, response::IntoResponse, Extension};

use crate::{
    error::{ErrorKind, HttpError},
    models::FilterUser,
    utils::decode_token,
    AppState,
};

#[derive(Debug, Clone)]
pub struct UserToken {
    pub user: FilterUser,
}

pub async fn jwt_auth(
    Extension(app_state): Extension<Arc<AppState>>,
    mut req: Request,
    next: Next,
) -> Result<impl IntoResponse, HttpError> {
    let token = req
        .headers()
        .get(header::AUTHORIZATION)
        .and_then(|auth_header| auth_header.to_str().ok())
        .and_then(|auth_value| {
            if auth_value.starts_with("Bearer ") {
                Some(auth_value[7..].to_string())
            } else {
                None
            }
        });

    let token =
        token.ok_or_else(|| HttpError::unauthorized(ErrorKind::TokenNotProvided.to_string()))?;

    let Ok(user_id) = decode_token(&token, app_state.config.jwt_secret_key.as_bytes()) else {
        return Err(HttpError::unauthorized(ErrorKind::InvalidToken.to_string()));
    };

    let user = app_state
        .db_client
        .get_user_by_id(&user_id)
        .await
        .map_err(|e| HttpError::server_error(e.to_string()))?;

    let user = user.ok_or_else(|| HttpError::unauthorized(ErrorKind::UserNotExist.to_string()))?;

    req.extensions_mut().insert(UserToken {
        user: FilterUser::from(user),
    });

    Ok(next.run(req).await)
}
